# -*- encoding: utf-8 -*-
'''
file       :binary_to_pic.py
Description: 对文件进行二进制转换后转为二维码, 并添加文件名字、文件类型以及hash校验等信息
Date       :2022/11/24 14:33:03
Author     :Josco
version    :python3.7.8
'''


from config import *
import time
import os
from hash_check import hash_check
from public import *
from generate_standard_pic import generate_standard_pic_method

wait_pic_matrix = cv.imread(wait_pic_path) # 加载等待图像的矩阵
open_machine_pic_matrix = cv.imread(open_machine_pic_path) # 加载开机的显示图像

class binary_to_pic(object):
    def __init__(self) -> None:
        pass

    def read_bits_from_file(self, file_name):
        """
        param:
            filename: 文件名
        return:
            data_bits: 将一个文件变成bits的一个np列表
        """
        data_bytes = np.fromfile(file_name, dtype="uint8")
        data_bits = np.unpackbits(data_bytes)
        print(data_bits)
        print("len(data_bits)", len(data_bits))
        return data_bits

    def read_binarydata(self, binary_data, width, height):
        """
        desc: 将一维数组的bits流list转为映射到[0-1]空间的二维list, 并完成相邻三个数组作为RGB通道的数组压缩操作
        param:
            binary_data : 二进制文件, 是一个list
            width: 最终成像像素的宽, height: 最终成像像素的高
        return:
            binary_data_list: reshape和转换后的二维数组, 数组中的数据范围在[0-1]
            pic_num: 根据数据可以形成width * height的图像张数
            remain_dat: 填充了多少个0
        """
        len_binary_data = len(binary_data)  # 原始二进制长度
        # 需要满足3个点合并为一个点后生成N 个widht *height高的图片，所以需要计算需添加的0的长度
        remain_data = height * width - len_binary_data % (width * height)
        if remain_data:
            remain_data_np = np.zeros(
                (int(remain_data)), dtype="uint8")  # 生成N个需要填充数据的nplsit
            binary_data = np.hstack(
                (binary_data, remain_data_np))  # 将两个nplist合并

        binary_data = binary_data.reshape(-1, width)  # reshape成width宽
        pic_num = int(len(binary_data) / height)  # 计算生成的照片张数
        print("文件生成的照片张数为：", pic_num)
        return binary_data, pic_num, remain_data

    def format_time(self, a_time):
        """转换时间格式"""
        return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(a_time))

    def get_file_info(self, file_name, pic_num, height, width, n, remain_data):
        """加载文件的详细信息，返回一个拼接好的字符串"""
        # 开始获取文件详细信息
        file_info = os.stat(file_name)
        c_time = self.format_time(file_info.st_ctime)  # 创建时间
        m_time = self.format_time(file_info.st_mtime)  # 修改时间
        a_time = self.format_time(file_info.st_atime)  # 访问时间
        # 添加文件信息
        # check_info = file_name + " " + str(c_time) + " " + str(m_time) + " " + str(a_time) + " " + str(
        #     width) + " " + str(height) + " " + str(n) + " " + str(remain_data) + " " + str(pic_num)
        check_info = file_name + " " + str(
            width) + " " + str(height) + " " + str(n) + " " + str(remain_data) + " " + str(pic_num)
        return check_info

    def pic_by_data(self, check_info, binary_data, pic_num, height, width, n=1):
        """基于binary_data进行画图, 为n*n个像素代表一个点, 并将hash结果还有一些文件信息等校验信息加入到开头"""
        do_hash_check = hash_check()  # 调用hash校验库

        pic_final_height = (height+check_len+4*3)*n # 计算得到最终单张图片的高度像素
        pic_final_width = (int(width / 3)+ 2*2) * n # 计算得到最终单张图片的宽度像素
        
        _row_to_black_list = [] # 存放为黑色的行
        _col_to_black_list = [] # 存放为黑色的列
        # _n = n-1 # 取余的被余数
        # 取得存放为黑色的行的list
        for _row_to_black in range(pic_final_height):
            if _row_to_black % n == 0:
                _row_to_black_list.append(_row_to_black)
        # 取得存放为黑色的列的list
        for _col_to_black in range(pic_final_width):
            if _col_to_black % n == 0:
                _col_to_black_list.append(_col_to_black)
        change_black_row_col = np.array([0,0,0])

        final_binary_mat = []  # 初始化最终写成img的所有矩阵的数组
        # 遍历pic_num张图像的数据
        for i in range(pic_num):
            # 获取当前数据
            local_binary_data = np.array(
                binary_data[i*height:(i+1)*height])  # 获取当前的pic块
            # print("local_binary_data", local_binary_data)
            # 添加md5校验信息
            hash_data = do_hash_check.md5(
                str(local_binary_data))  # 计算当前块的hash值
            # jiezhidaozhe作为尾部校验信息，当解码时前两行的数据转为str类型后遇到jiezhidaozhe则证明基本信息和校验信息就到这里了
            
            check_info_data = check_info + " " + \
                str(i) + " " + hash_data + " " + \
                is_pic_bit  # 结合文件基本信息、当前第几张图像和校验信息最后还有尾部信息
            check_info_data_bit = do_hash_check.str2bitarray(
                check_info_data)  # 将信息转为bitlist
            check_info_data_bit = np.array(
                list(check_info_data_bit), dtype="uint8")  # 转为nparray
            # 校验信息默认占前四行，一行01长度为width,三行的长度为width*3，当前校验信息长度小于width*4，大概在900个0或1的长度，所以就不加判断了
            
            check_info_remain_data = (width * check_len) - \
                len(check_info_data_bit)  # 看校验信息需要添加多少个0
            check_info_remain_data_np = np.zeros(
                (int(check_info_remain_data)), dtype="uint8")
            check_info_data_bit = np.hstack(
                (check_info_data_bit, check_info_remain_data_np))  # 把校验信息的bitarray和需添加的多少个0加起来

            # 对前4行所有校验数据进行映射和reshape操作
            check_info_data_bit = np.array(check_info_data_bit)
            # 将校验信息reshape成check_len行width宽的数组
            check_info_data_bit = check_info_data_bit.reshape(-1, width)
            local_binary_data = np.concatenate(
                (check_info_data_bit, local_binary_data), axis=0)  # 拼接校验信息和文件编码信息
            # 转为3维数组
            local_binary_data = local_binary_data.reshape(-1, int(width/3), 3)
            # 把一张相片分为均等三分
            local_binary_data_h, local_binary_data_w, c = local_binary_data.shape
            every_h = int(local_binary_data_h / 3)

            # 添加第一块黑色包围
            black_top_bottom = np.zeros(
                (1, local_binary_data[0:every_h].shape[1], 3), dtype="uint8")
            final_local_binary_data_1 = np.concatenate(
                (black_top_bottom, local_binary_data[0:every_h], black_top_bottom), axis=0)
            black_left_right = np.zeros(
                (final_local_binary_data_1.shape[0], 1, 3), dtype="uint8")
            final_local_binary_data_1 = np.concatenate(
                (black_left_right, final_local_binary_data_1, black_left_right), axis=1)
            # 创建height行，1列的白色包围，并添加到第一块的白色包围
            white_top_bottom = np.zeros(
                (1, final_local_binary_data_1.shape[1], 3), dtype="uint8")
            final_local_binary_data_1 = np.concatenate(
                (white_top_bottom, final_local_binary_data_1, white_top_bottom), axis=0)
            white_left_right = np.zeros(
                (final_local_binary_data_1.shape[0], 1, 3), dtype="uint8")
            final_local_binary_data_1 = np.concatenate(
                (white_left_right, final_local_binary_data_1, white_left_right), axis=1)
            # 添加第二块的黑白包围
            final_local_binary_data_2 = np.concatenate(
                (black_top_bottom, local_binary_data[every_h:every_h*2], black_top_bottom), axis=0)
            final_local_binary_data_2 = np.concatenate(
                (black_left_right, final_local_binary_data_2, black_left_right), axis=1)
            final_local_binary_data_2 = np.concatenate(
                (white_top_bottom, final_local_binary_data_2, white_top_bottom), axis=0)
            final_local_binary_data_2 = np.concatenate(
                (white_left_right, final_local_binary_data_2, white_left_right), axis=1)
            # 添加第三块的黑白包围
            final_local_binary_data_3 = np.concatenate(
                (black_top_bottom, local_binary_data[every_h*2:], black_top_bottom), axis=0)
            final_local_binary_data_3 = np.concatenate(
                (black_left_right, final_local_binary_data_3, black_left_right), axis=1)
            final_local_binary_data_3 = np.concatenate(
                (white_top_bottom, final_local_binary_data_3, white_top_bottom), axis=0)
            final_local_binary_data_3 = np.concatenate(
                (white_left_right, final_local_binary_data_3, white_left_right), axis=1)
            # 把添加好黑白包围的图片拼接
            final_local_binary_data = np.concatenate(
                (final_local_binary_data_1, final_local_binary_data_2, final_local_binary_data_3), axis=0)

            # 按照放大倍数（n倍）的方式存储单张伪二维码图像
            h, w, c = final_local_binary_data.shape
            local_binary_data1 = np.tile(
                final_local_binary_data, (n, n))  # 宽高都复制n遍，也就是放大n倍
            # reshape成合理的宽高
            local_binary_data1 = local_binary_data1.reshape(-1, w * n, 3)

            #对照片每个小色块加上隔离段, 从5*5的色块变成中心3*3个像素点为颜色, 最外层为黑色隔离带的色块
            local_binary_data1[_row_to_black_list,:,:] = change_black_row_col
            local_binary_data1[:,_col_to_black_list,:] = change_black_row_col
            # 把数字矩阵从0/1映射到0/255
            local_binary_data1 = local_binary_data1 * 255
            # # 存储单张图像，三大块组合而成的图像
            cv.imwrite(bmp_path + str(i) + ".bmp",
                       local_binary_data1)  # 存为图像

            # 将每张图像的03区间的数据添加到final_binary_mat中
            final_binary_mat.append(local_binary_data1)

        if pic_num % 3 != 0:
            # 因为是3张图片合成一个图片, 所以当pic_num不是3的倍数的时候, 需要额外添加纯黑色的mat, 用于最后一张图像的合成
            append_mat = np.zeros(local_binary_data1.shape, dtype="uint8")
            for append_mat_len_item in range(3 - (pic_num % 3)):
                final_binary_mat.append(append_mat)
            append_mat_num = 3 - (pic_num % 3)
        else:
            append_mat_num = 0
        # 因此现在该文件最终产生的照片张数是pic_num + append_mat_num
        all_pic_sum = pic_num + append_mat_num

        """现在开始展示开机图像"""
        print("现在开始展示开机图像")
        show_pic(open_machine_pic_matrix, wait_pic_time)
        print("编码部分成功开机，开始进行编码")

        # 按照三张合成一张照片的方式合成照片并按照n的大小进行显示
        for j in range(int(all_pic_sum/3)):
            # 将邻近的三个图片合为一张图片
            _final_binary_mat = np.concatenate(
                (final_binary_mat[j*3], final_binary_mat[j*3+1], final_binary_mat[j*3+2]), axis=1)
            # 把矩阵存为bmp文件
            pic_name = "./data1/" + str(j) + "++.bmp"
            cv.imwrite(pic_name, _final_binary_mat)  # 存为图像
            show_pic(_final_binary_mat, screen_pic_FPS)
        # # 删除编码产生的所有图片
        # del_file(screen_pic_path)

        # 显示等待的图片
        print("文件编码结束，开始进行等待状态")
        show_pic(wait_pic_matrix, wait_pic_time)

        return None


    def do_binary_to_pic(self, file_name):
        """
        desc: 把文件转为伪二维码
        param:
        file_name: 文件绝对路径
        return:
            NONE
        """
        # # 系统启动，进行编码部分初始化
        # #（1）先进行标准化图像展示部分
        # do_generate_standard_pic = generate_standard_pic()
        # do_generate_standard_pic.generate_st_pic()

        time_start = time.time()
        # 读取文件的bits list
        binary_data = self.read_bits_from_file(file_name)

        # 当前使用的n,widht,和宽, n * n个像素代表一个点, 通过修改index_list[n]的参数来调整图像到底是由n*n个像素表示为一个点
        n, width, height = local_list[0], local_list[1], local_list[2]
        # 对二进制文件进行读取、分块并填充0
        binary_data_list, pic_num, remain_data = self.read_binarydata(
            binary_data, width, height)
        # 加载文件详细信息
        check_info = self.get_file_info(
            file_name, pic_num, height, width, n, remain_data)
        # 画图
        self.pic_by_data(
            check_info, binary_data_list, pic_num, height, width, n)

        time_end = time.time()
        time_sum = time_end - time_start


def main():
    # 系统启动，进行编码部分初始化
    #（1）先进行标准化图像展示部分
    do_generate_standard_pic = generate_standard_pic_method()
    do_generate_standard_pic.generate_st_pic()

    time_start = time.time()
    # 读取文件的bits list
    do_binary_to_pic = binary_to_pic()
    binary_data = do_binary_to_pic.read_bits_from_file(file_name)

    # 当前使用的n,widht,和宽, n * n个像素代表一个点, 通过修改index_list[n]的参数来调整图像到底是由n*n个像素表示为一个点
    n, width, height = local_list[0], local_list[1], local_list[2]
    # 对二进制文件进行读取、分块并填充0
    binary_data_list, pic_num, remain_data = do_binary_to_pic.read_binarydata(
        binary_data, width, height)
    # 加载文件详细信息
    check_info = do_binary_to_pic.get_file_info(
        file_name, pic_num, height, width, n, remain_data)
    # 画图
    do_binary_to_pic.pic_by_data(
        check_info, binary_data_list, pic_num, height, width, n)

    time_end = time.time()
    time_sum = time_end - time_start
    print(time_sum)


if __name__ == '__main__':
    main()
